import { router, withAppProcedure } from '@/server/trpc-middlewares/trpc';
import z from 'zod';
import type { PutObjectCommandInput } from '@aws-sdk/client-s3';
import { PutObjectCommand, S3Client } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
import { db } from '../db/db';
import { files } from '../db/schema';
import { and, asc, desc, eq, isNull, sql } from 'drizzle-orm';
import { filesCanOrderByColumns } from '../db/validate-schema';
import { TRPCError } from '@trpc/server';

const filesOrderByColumnSchema = z
  .object({
    field: filesCanOrderByColumns.keyof(),
    order: z.enum(['desc', 'asc']),
  })
  .optional();

export type FilesOrderByColumn = z.infer<typeof filesOrderByColumnSchema>;

export const fileOpenRoutes = router({
  createPresignedUrl: withAppProcedure
    .input(
      z.object({
        filename: z.string(),
        contentType: z.string(),
        size: z.number(),
        appId: z.string(),
      })
    )
    .mutation(async ({ ctx, input }) => {
      const date = new Date();

      const isoString = date.toISOString();

      const dateString = isoString.split('T')[0];

      const app = await db.query.apps.findFirst({
        where: (apps, { eq, and }) =>
          and(eq(apps.id, input.appId), eq(apps.userId, ctx.user.id)),
        with: {
          storage: true,
        },
      });

      if (!app || !app.storage) {
        throw new TRPCError({
          code: 'BAD_REQUEST',
        });
      }

      const config = app.storage.configuration;

      const s3Client = new S3Client({
        endpoint: config.apiEndpoint,
        region: config.region,
        credentials: {
          accessKeyId: config.accessKeyId,
          secretAccessKey: config.secretAccessKey,
        },
      });

      const params: PutObjectCommandInput = {
        Bucket: config.bucket,
        Key: `${dateString}/${input.filename.replaceAll(' ', '_')}`,
        ContentType: input.contentType,
        ContentLength: input.size,
      };

      const command = new PutObjectCommand(params);
      const url: string = await getSignedUrl(s3Client, command, {
        expiresIn: 60,
      });

      return {
        url,
        method: 'PUT' as const,
      };
    }),

  saveFile: withAppProcedure
    .input(
      z.object({
        name: z.string(),
        path: z.string(),
        type: z.string(),
        appId: z.string(),
      })
    )
    .mutation(async ({ ctx, input }) => {
      const { user } = ctx;
      const url = new URL(input.path);
      const prePathnames = url.pathname.split('/');
      prePathnames[prePathnames.length - 1] = input.name;
      const photos = await db
        .insert(files)
        .values({
          ...input,
          path: prePathnames.join('/'),
          url: url.toString(),
          userId: user.id,
          contentType: input.type,
        })
        .returning();

      const res = photos[0];

      return res;
    }),
  infinityQueryFiles: withAppProcedure
    .input(
      z.object({
        cursor: z
          .object({
            id: z.string(),
            createdAt: z.string(),
          })
          .optional(),
        limit: z.number().default(10),
        orderBy: filesOrderByColumnSchema,
        withDeleted: z.boolean().optional().default(false),
        appId: z.string(),
      })
    )
    .query(async ({ input, ctx }) => {
      const {
        cursor,
        limit,
        orderBy = {
          order: 'desc',
          field: 'createdAt',
        },
        withDeleted = false,
        appId,
      } = input;

      const deletedFilter = withDeleted ? undefined : isNull(files.deletedAt);
      const userFilter = eq(files.userId, ctx.user.id);
      const appIdFilter = eq(files.appId, appId);
      const cursorFilter = cursor
        ? orderBy.order === 'desc'
          ? sql`(to_char("files"."created_at", 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'), "files"."id") < (${new Date(cursor.createdAt).toISOString()}, ${cursor.id})`
          : sql`(to_char("files"."created_at", 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'), "files"."id") > (${new Date(cursor.createdAt).toISOString()}, ${cursor.id})`
        : undefined;

      const statement = db
        .select()
        .from(files)
        .where(and(deletedFilter, cursorFilter, appIdFilter, userFilter))
        .limit(limit);

      statement.orderBy(
        orderBy.order === 'desc'
          ? desc(files[orderBy.field])
          : asc(files[orderBy.field])
      );

      const res = await statement;
      return {
        items: res,
        nextCursor: res[res.length - 1]
          ? {
              createdAt: res[res.length - 1].createdAt!,
              id: res[res.length - 1].id,
            }
          : null,
      };
    }),

  deleteFile: withAppProcedure
    .input(z.string())
    .mutation(async ({ ctx, input }) => {
      return db
        .update(files)
        .set({
          deletedAt: new Date(),
        })
        .where(and(eq(files.id, input), eq(files.userId, ctx.user.id)));
    }),
});
